{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This cell is added by sphinx-gallery\n",
    "# It can be customized to whatever you like\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "Basic tutorial: qubit rotation\n",
    "==============================\n",
    "\n",
    ".. meta::\n",
    "    :property=\"og:description\": To see how PennyLane allows the easy construction and optimization\n",
    "        of quantum functions, let's consider the 'hello world' of QML: qubit rotation.\n",
    "    :property=\"og:image\": https://pennylane.ai/qml/_images/bloch.png\n",
    "\n",
    ".. related::\n",
    "\n",
    "   tutorial_plugins_hybrid Plugins and Hybrid computation\n",
    "   tutorial_gaussian_transformation Gaussian transformation\n",
    "   tutorial_state_preparation Training a quantum circuit with PyTorch\n",
    "\n",
    "*Author: PennyLane dev team. Last updated: 19 Jan 2021.*\n",
    "\n",
    "To see how PennyLane allows the easy construction and optimization of quantum functions, let's\n",
    "consider the simple case of **qubit rotation** the PennyLane version of the 'Hello, world!'\n",
    "example.\n",
    "\n",
    "The task at hand is to optimize two rotation gates in order to flip a single\n",
    "qubit from state $\\left|0\\right\\rangle$ to state $\\left|1\\right\\rangle$.\n",
    "\n",
    "\n",
    "The quantum circuit\n",
    "-------------------\n",
    "\n",
    "In the qubit rotation example, we wish to implement the following quantum circuit:\n",
    "\n",
    ".. figure:: ../demonstrations/qubit_rotation/rotation_circuit.png\n",
    "    :align: center\n",
    "    :width: 40%\n",
    "    :target: javascript:void(0);\n",
    "\n",
    "Breaking this down step-by-step, we first start with a qubit in the ground state\n",
    "$|0\\rangle = \\begin{bmatrix}1 & 0 \\end{bmatrix}^T$,\n",
    "and rotate it around the x-axis by applying the gate\n",
    "\n",
    "\\begin{align}R_x(\\phi_1) = e^{-i \\phi_1 \\sigma_x /2} =\n",
    "    \\begin{bmatrix} \\cos \\frac{\\phi_1}{2} &  -i \\sin \\frac{\\phi_1}{2} \\\\\n",
    "                   -i \\sin \\frac{\\phi_1}{2} &  \\cos \\frac{\\phi_1}{2}\n",
    "    \\end{bmatrix},\\end{align}\n",
    "\n",
    "and then around the y-axis via the gate\n",
    "\n",
    "\\begin{align}R_y(\\phi_2) = e^{-i \\phi_2 \\sigma_y/2} =\n",
    "   \\begin{bmatrix} \\cos \\frac{\\phi_2}{2} &  - \\sin \\frac{\\phi_2}{2} \\\\\n",
    "                   \\sin \\frac{\\phi_2}{2} &  \\cos \\frac{\\phi_2}{2}\n",
    "   \\end{bmatrix}.\\end{align}\n",
    "\n",
    "After these operations the qubit is now in the state\n",
    "\n",
    "\\begin{align}| \\psi \\rangle = R_y(\\phi_2) R_x(\\phi_1) | 0 \\rangle.\\end{align}\n",
    "\n",
    "Finally, we measure the expectation value $\\langle \\psi \\mid \\sigma_z \\mid \\psi \\rangle$\n",
    "of the Pauli-Z operator\n",
    "\n",
    "\\begin{align}\\sigma_z =\n",
    "   \\begin{bmatrix} 1 &  0 \\\\\n",
    "                   0 & -1\n",
    "   \\end{bmatrix}.\\end{align}\n",
    "\n",
    "Using the above to calculate the exact expectation value, we find that\n",
    "\n",
    "\\begin{align}\\langle \\psi \\mid \\sigma_z \\mid \\psi \\rangle\n",
    "    = \\langle 0 \\mid R_x(\\phi_1)^\\dagger R_y(\\phi_2)^\\dagger \\sigma_z  R_y(\\phi_2) R_x(\\phi_1) \\mid 0 \\rangle\n",
    "    = \\cos(\\phi_1)\\cos(\\phi_2).\\end{align}\n",
    "\n",
    "Depending on the circuit parameters $\\phi_1$ and $\\phi_2$, the\n",
    "output expectation lies between $1$ (if $\\left|\\psi\\right\\rangle = \\left|0\\right\\rangle$)\n",
    "and $-1$ (if $\\left|\\psi\\right\\rangle = \\left|1\\right\\rangle$).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see how we can easily implement and optimize this circuit using PennyLane.\n",
    "\n",
    "Importing PennyLane and NumPy\n",
    "-----------------------------\n",
    "\n",
    "The first thing we need to do is import PennyLane, as well as the wrapped version\n",
    "of NumPy provided by PennyLane.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pennylane as qml\n",
    "from pennylane import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ".. important::\n",
    "\n",
    "    When constructing a hybrid quantum/classical computational model with PennyLane,\n",
    "    it is important to **always import NumPy from PennyLane**, not the standard NumPy!\n",
    "\n",
    "    By importing the wrapped version of NumPy provided by PennyLane, you can combine\n",
    "    the power of NumPy with PennyLane:\n",
    "\n",
    "    * continue to use the classical NumPy functions and arrays you know and love\n",
    "    * combine quantum functions (evaluated on quantum hardware/simulators) and\n",
    "      classical functions (provided by NumPy)\n",
    "    * allow PennyLane to automatically calculate gradients of both classical and\n",
    "      quantum functions\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Creating a device\n",
    "-----------------\n",
    "\n",
    "Before we can construct our quantum node, we need to initialize a **device**.\n",
    "\n",
    ".. admonition:: Definition\n",
    "    :class: defn\n",
    "\n",
    "    Any computational object that can apply quantum operations and return a measurement value\n",
    "    is called a quantum **device**.\n",
    "\n",
    "    In PennyLane, a device could be a hardware device (such as the IBM QX4, via the\n",
    "    PennyLane-PQ plugin), or a software simulator (such as Strawberry Fields, via the\n",
    "    PennyLane-SF plugin).\n",
    "\n",
    ".. tip::\n",
    "\n",
    "   *Devices are loaded in PennyLane via the function* :func:`~.pennylane.device`\n",
    "\n",
    "\n",
    "PennyLane supports devices using both the qubit model of quantum computation and devices\n",
    "using the CV model of quantum computation. In fact, even a hybrid computation containing\n",
    "both qubit and CV quantum nodes is possible; see the\n",
    "`hybrid computation example <hybrid_computation_example>` for more details.\n",
    "\n",
    "For this tutorial, we are using the qubit model, so let's initialize the ``'default.qubit'`` device\n",
    "provided by PennyLane; a simple pure-state qubit simulator.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "dev1 = qml.device(\"default.qubit\", wires=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For all devices, :func:`~.pennylane.device` accepts the following arguments:\n",
    "\n",
    "* ``name``: the name of the device to be loaded\n",
    "* ``wires``: the number of subsystems to initialize the device with\n",
    "\n",
    "Here, as we only require a single qubit for this example, we set ``wires=1``.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Constructing the QNode\n",
    "----------------------\n",
    "\n",
    "Now that we have initialized our device, we can begin to construct a\n",
    "**quantum node** (or QNode).\n",
    "\n",
    "\n",
    ".. admonition:: Definition\n",
    "    :class: defn\n",
    "\n",
    "    QNodes are an abstract encapsulation of a quantum function, described by a\n",
    "    quantum circuit. QNodes are bound to a particular quantum device, which is\n",
    "    used to evaluate expectation and variance values of this circuit.\n",
    "\n",
    ".. tip::\n",
    "\n",
    "   *QNodes can be constructed via the* :class:`~.pennylane.QNode`\n",
    "   *class, or by using the provided* :func:`~.pennylane.qnode` decorator.\n",
    "\n",
    "First, we need to define the quantum function that will be evaluated in the QNode:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def circuit(params):\n",
    "    qml.RX(params[0], wires=0)\n",
    "    qml.RY(params[1], wires=0)\n",
    "    return qml.expval(qml.PauliZ(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a simple circuit, matching the one described above.\n",
    "Notice that the function ``circuit()`` is constructed as if it were any\n",
    "other Python function; it accepts a positional argument ``params``, which may\n",
    "be a list, tuple, or array, and uses the individual elements for gate parameters.\n",
    "\n",
    "However, quantum functions are a **restricted subset** of Python functions.\n",
    "For a Python function to also be a valid quantum function, there are some\n",
    "important restrictions:\n",
    "\n",
    "* **Quantum functions must contain quantum operations, one operation per line,\n",
    "  in the order in which they are to be applied.**\n",
    "\n",
    "  In addition, we must always specify the subsystem the operation applies to,\n",
    "  by passing the ``wires`` argument; this may be a list or an integer, depending\n",
    "  on how many wires the operation acts on.\n",
    "\n",
    "  For a full list of quantum operations, see :doc:`the documentation <introduction/operations>`.\n",
    "\n",
    "* **Quantum functions must return either a single or a tuple of measured observables**.\n",
    "\n",
    "  As a result, the quantum function always returns a classical quantity, allowing\n",
    "  the QNode to interface with other classical functions (and also other QNodes).\n",
    "\n",
    "  For a full list of observables, see :doc:`the documentation <introduction/operations>`.\n",
    "  The documentation also provides details on supported :doc:`measurement return types <introduction/measurements>`.\n",
    "\n",
    "<div class=\"alert alert-info\"><h4>Note</h4><p>Certain devices may only support a subset of the available PennyLane\n",
    "    operations/observables, or may even provide additional operations/observables.\n",
    "    Please consult the documentation for the plugin/device for more details.</p></div>\n",
    "\n",
    "Once we have written the quantum function, we convert it into a :class:`~.pennylane.QNode` running\n",
    "on device ``dev1`` by applying the :func:`~.pennylane.qnode` decorator.\n",
    "**directly above** the function definition:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qml.qnode(dev1)\n",
    "def circuit(params):\n",
    "    qml.RX(params[0], wires=0)\n",
    "    qml.RY(params[1], wires=0)\n",
    "    return qml.expval(qml.PauliZ(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Thus, our ``circuit()`` quantum function is now a :class:`~.pennylane.QNode`, which will run on\n",
    "device ``dev1`` every time it is evaluated.\n",
    "\n",
    "To evaluate, we simply call the function with some appropriate numerical inputs:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.8515405859048368\n"
     ]
    }
   ],
   "source": [
    "print(circuit([0.54, 0.12]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Calculating quantum gradients\n",
    "-----------------------------\n",
    "\n",
    "The gradient of the function ``circuit``, encapsulated within the ``QNode``,\n",
    "can be evaluated by utilizing the same quantum\n",
    "device (``dev1``) that we used to evaluate the function itself.\n",
    "\n",
    "PennyLane incorporates both analytic differentiation, as well as numerical\n",
    "methods (such as the method of finite differences). Both of these are done\n",
    "automatically.\n",
    "\n",
    "We can differentiate by using the built-in :func:`~.pennylane.grad` function.\n",
    "This returns another function, representing the gradient (i.e., the vector of\n",
    "partial derivatives) of ``circuit``. The gradient can be evaluated in the same\n",
    "way as the original function:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "dcircuit = qml.grad(circuit, argnum=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The function :func:`~.pennylane.grad` itself **returns a function**, representing\n",
    "the derivative of the QNode with respect to the argument specified in ``argnum``.\n",
    "In this case, the function ``circuit`` takes one argument (``params``), so we\n",
    "specify ``argnum=0``. Because the argument has two elements, the returned gradient\n",
    "is two-dimensional. We can then evaluate this gradient function at any point in the parameter space.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[array(-0.51043865), array(-0.1026782)]\n"
     ]
    }
   ],
   "source": [
    "print(dcircuit([0.54, 0.12]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**A note on arguments**\n",
    "\n",
    "Quantum circuit functions, being a restricted subset of Python functions,\n",
    "can also make use of multiple positional arguments and keyword arguments.\n",
    "For example, we could have defined the above quantum circuit function using\n",
    "two positional arguments, instead of one array argument:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qml.qnode(dev1)\n",
    "def circuit2(phi1, phi2):\n",
    "    qml.RX(phi1, wires=0)\n",
    "    qml.RY(phi2, wires=0)\n",
    "    return qml.expval(qml.PauliZ(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we calculate the gradient for such a function, the usage of ``argnum``\n",
    "will be slightly different. In this case, ``argnum=0`` will return the gradient\n",
    "with respect to only the first parameter (``phi1``), and ``argnum=1`` will give\n",
    "the gradient for ``phi2``. To get the gradient with respect to both parameters,\n",
    "we can use ``argnum=[0,1]``:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(array(-0.51043865), array(-0.1026782))\n"
     ]
    }
   ],
   "source": [
    "dcircuit = qml.grad(circuit2, argnum=[0, 1])\n",
    "print(dcircuit(0.54, 0.12))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Keyword arguments may also be used in your custom quantum function. PennyLane\n",
    "does **not** differentiate QNodes with respect to keyword arguments,\n",
    "so they are useful for passing external data to your QNode.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Optimization\n",
    "------------\n",
    "\n",
    ".. admonition:: Definition\n",
    "    :class: defn\n",
    "\n",
    "    If using the default NumPy/Autograd interface, PennyLane provides a collection\n",
    "    of optimizers based on gradient descent. These optimizers accept a cost function\n",
    "    and initial parameters, and utilize PennyLane's automatic differentiation\n",
    "    to perform gradient descent.\n",
    "\n",
    ".. tip::\n",
    "\n",
    "   *See* :doc:`introduction/optimizers` *for details and documentation of available optimizers*\n",
    "\n",
    "Next, let's make use of PennyLane's built-in optimizers to optimize the two circuit\n",
    "parameters $\\phi_1$ and $\\phi_2$ such that the qubit, originally in state\n",
    "$\\left|0\\right\\rangle$, is rotated to be in state $\\left|1\\right\\rangle$. This is equivalent to measuring a\n",
    "Pauli-Z expectation value of $-1$, since the state $\\left|1\\right\\rangle$ is an eigenvector\n",
    "of the Pauli-Z matrix with eigenvalue $\\lambda=-1$.\n",
    "\n",
    "In other words, the optimization procedure will find the weights\n",
    "$\\phi_1$ and $\\phi_2$ that result in the following rotation on the Bloch sphere:\n",
    "\n",
    ".. figure:: ../demonstrations/qubit_rotation/bloch.png\n",
    "    :align: center\n",
    "    :width: 70%\n",
    "    :target: javascript:void(0);\n",
    "\n",
    "To do so, we need to define a **cost** function. By *minimizing* the cost function, the\n",
    "optimizer will determine the values of the circuit parameters that produce the desired outcome.\n",
    "\n",
    "In this case, our desired outcome is a Pauli-Z expectation value of $-1$. Since we\n",
    "know that the Pauli-Z expectation is bound between $[-1, 1]$, we can define our\n",
    "cost directly as the output of the QNode:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cost(x):\n",
    "    return circuit(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To begin our optimization, let's choose small initial values of $\\phi_1$ and $\\phi_2$:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.9998675058299389\n"
     ]
    }
   ],
   "source": [
    "init_params = np.array([0.011, 0.012])\n",
    "print(cost(init_params))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that, for these initial parameter values, the cost function is close to $1$.\n",
    "\n",
    "Finally, we use an optimizer to update the circuit parameters for 100 steps. We can use the built-in\n",
    ":class:`~.pennylane.GradientDescentOptimizer` class:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cost after step     5:  0.9961778\n",
      "Cost after step    10:  0.8974944\n",
      "Cost after step    15:  0.1440490\n",
      "Cost after step    20: -0.1536720\n",
      "Cost after step    25: -0.9152496\n",
      "Cost after step    30: -0.9994046\n",
      "Cost after step    35: -0.9999964\n",
      "Cost after step    40: -1.0000000\n",
      "Cost after step    45: -1.0000000\n",
      "Cost after step    50: -1.0000000\n",
      "Cost after step    55: -1.0000000\n",
      "Cost after step    60: -1.0000000\n",
      "Cost after step    65: -1.0000000\n",
      "Cost after step    70: -1.0000000\n",
      "Cost after step    75: -1.0000000\n",
      "Cost after step    80: -1.0000000\n",
      "Cost after step    85: -1.0000000\n",
      "Cost after step    90: -1.0000000\n",
      "Cost after step    95: -1.0000000\n",
      "Cost after step   100: -1.0000000\n",
      "Optimized rotation angles: [7.15266381e-18 3.14159265e+00]\n"
     ]
    }
   ],
   "source": [
    "# initialise the optimizer\n",
    "opt = qml.GradientDescentOptimizer(stepsize=0.4)\n",
    "\n",
    "# set the number of steps\n",
    "steps = 100\n",
    "# set the initial parameter values\n",
    "params = init_params\n",
    "\n",
    "for i in range(steps):\n",
    "    # update the circuit parameters\n",
    "    params = opt.step(cost, params)\n",
    "\n",
    "    if (i + 1) % 5 == 0:\n",
    "        print(\"Cost after step {:5d}: {: .7f}\".format(i + 1, cost(params)))\n",
    "\n",
    "print(\"Optimized rotation angles: {}\".format(params))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that the optimization converges after approximately 40 steps.\n",
    "\n",
    "Substituting this into the theoretical result $\\langle \\psi \\mid \\sigma_z \\mid \\psi \\rangle = \\cos\\phi_1\\cos\\phi_2$,\n",
    "we can verify that this is indeed one possible value of the circuit parameters that\n",
    "produces $\\langle \\psi \\mid \\sigma_z \\mid \\psi \\rangle=-1$, resulting in the qubit being rotated\n",
    "to the state $\\left|1\\right\\rangle$.\n",
    "\n",
    "<div class=\"alert alert-info\"><h4>Note</h4><p>Some optimizers, such as :class:`~.pennylane.AdagradOptimizer`, have\n",
    "    internal hyperparameters that are stored in the optimizer instance. These can\n",
    "    be reset using the :meth:`reset` method.</p></div>\n",
    "\n",
    "Continue on to the next tutorial, `gaussian_transformation`, to see a similar example using\n",
    "continuous-variable (CV) quantum nodes.\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "conda_braket",
   "language": "python",
   "name": "conda_braket"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
